/*
 * Copyright 2011-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0.
 * See `LICENSE` in the project root for license information.
 */

package me.ijleex.mgmt.framework.json.autoconfigure;

import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.TimeZone;

import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

/**
 * 配置 Jackson2
 *
 * @author liym
 * @since 2019-03-19 23:57 新建
 */
@AutoConfigureBefore(JacksonAutoConfiguration.class)
@Configuration
public class Jackson2Configuration {

    private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");

    public Jackson2Configuration() {
    }

    /**
     * 构建 ObjectMapper
     *
     * @param builder 构造器
     * @return {@link ObjectMapper}
     * @see org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#handleReturnValue
     * @see org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter#writeInternal
     * @since 2019-03-19 23:58
     */
    @ConditionalOnMissingBean(ObjectMapper.class)
    @Primary
    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        JsonFactory factory = JsonFactory.builder()
                // 允许出现特殊字符和转义符
                .enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS)
                // 允许出现单引号
                .enable(JsonReadFeature.ALLOW_SINGLE_QUOTES)
                .build();

        ObjectMapper objectMapper = builder.createXmlMapper(false)
                .factory(factory)
                // 设置日期格式
                .simpleDateFormat(DATE_TIME_PATTERN)
                // 设置为中国上海时区
                .locale(Locale.CHINA)
                .timeZone(TimeZone.getTimeZone("Asia/Shanghai"))
                .build();

        // 通过该方法对mapper对象进行设置，所有序列化的对象都将按改规则进行系列化
        // Include.Include.ALWAYS 默认
        // Include.NON_DEFAULT 属性为默认值不序列化
        // Include.NON_EMPTY 属性为 空（""） 或者为 NULL 都不序列化，则返回的json是没有这个字段的。这样对移动端会更省流量
        // Include.NON_NULL 属性为 NULL 不序列化
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 字段保留，将 NULL 值转为 ""
        objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<>() {
            @Override
            public void serialize(Object o, JsonGenerator gen, SerializerProvider provider)
                    throws IOException {
                gen.writeString("");
            }
        });

        return objectMapper;
    }

    /**
     * 定制 Jackson ObjectMapper，修改 JDK8 Date-Time 日期处理（序列化/反序列化）
     *
     * <p><b>按照 <a href="https://github.com/battcn/request-spring-boot">battcn/request-spring-boot</a>
     * | LocalDateSerializerConfig 改造而成。</b></p>
     *
     * @return {@link Jackson2ObjectMapperBuilderCustomizer}
     * @since 2019-04-28 10:25
     */
    @Bean
    public Jackson2ObjectMapperBuilderCustomizer objectMapperBuilderCustomizer() {
        return (builder) -> builder.serializerByType(LocalDateTime.class, this.dateTimeSerializer())
                .serializerByType(LocalDate.class, this.dateSerializer())
                .serializerByType(LocalTime.class, this.timeSerializer())
                .deserializerByType(LocalDateTime.class, this.dateTimeDeserializer())
                .deserializerByType(LocalDate.class, this.dateDeserializer())
                .deserializerByType(LocalTime.class, this.timeDeserializer());
    }

    private LocalDateTimeSerializer dateTimeSerializer() {
        return new LocalDateTimeSerializer(DATETIME_FORMATTER);
    }

    private LocalDateSerializer dateSerializer() {
        return new LocalDateSerializer(DATE_FORMATTER);
    }

    private LocalTimeSerializer timeSerializer() {
        return new LocalTimeSerializer(TIME_FORMATTER);
    }

    private LocalDateTimeDeserializer dateTimeDeserializer() {
        return new LocalDateTimeDeserializer(DATETIME_FORMATTER);
    }

    private LocalDateDeserializer dateDeserializer() {
        return new LocalDateDeserializer(DATE_FORMATTER);
    }

    private LocalTimeDeserializer timeDeserializer() {
        return new LocalTimeDeserializer(TIME_FORMATTER);
    }

}
